01.4 精通自定义 View 之绘图基础——Region

返回自定义 View 目录

1.4.1 构造 Region

1. 直接构造

1
2
3
4
5
6
// 复制一个 Region 的范围,不常用
public Region(Region region)
// 创建一个矩形区域
public Region(Rect r)
// 创建一个矩形区域
public Region(int left, int top, int right, int bottom)

示例:

1
2
3
4
5
6
7
8
9
10
Region region = new Region(new Rect(50,50,200,100));
drawRegion(canvas,region,paint);
private void drawRegion (Canvas canvas, Region rgn, Paint paint) {
RegionIterator iter = new RegionIterator(rgn);
Rect r = new Rect();
while (iter.next(r)) {
canvas.drawRect(r, paint);
}
}

Canvas 并没有提供针对 Region 的绘图方法,这就说明 Region 的本意并不是用来绘图的。对于上面构造的矩形填充,我们完全可以使用 Rect 来代替。

2. 间接构造

间接构造主要是通过 public Region()的空构造函数与 set 系列函数相结合来实现的。

1
2
3
4
5
6
7
8
9
// 空构造函数
public Region()
// set 系列
public void setEmpty() // 置空
public boolean set(Region region)
public boolean set(Rect r)
public boolean set(int left, int top, int right, int bottom)
public boolean setPath(Path path, Region clip)

无论调用 set 系列函数的 Region 是不是有区域值,当调用 set 系列函数后,原来的区域值就会被替换成 set 系列函数里的区域值。

其他函数不表,重点介绍 setPath() 函数:

1
boolean setPath(Path path, Region clip)

参数:
Path path:用来构造区域的路径。
Region clip:与前面的 path 所构成的路径取交集,并将该交集设置为最终的区域。

由于路径有很多种构造方法,而且可以轻易构造出非矩形的路径,因而摆脱了前面的构造函数只能构造矩形区域的限制。但这里有一个问题,即需要指定另一个区域来取交集。当然,如果想显示路径构造的区域,那么 Region clip 参数可以传入一个比 Path 范围大得多的区域,取完交集之后,当然就是 Path path 参数所对应的区域了。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class BaseView extends View {
private Paint paint;
private Region region;
public BaseView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
paint = new Paint();
paint.setColor(Color.RED);
paint.setStyle(Paint.Style.FILL);
// 构造一条椭圆路径
Path ovalPath = new Path();
RectF rectF = new RectF(50, 50, 200, 500);
ovalPath.addOval(rectF, Path.Direction.CCW);
// 在 setPath 中传入一个比椭圆区域小的矩形区域,让其取交集
region = new Region();
region.setPath(ovalPath, new Region(50, 50, 200, 200));
}
@Override
protected void onDraw(Canvas canvas) {
drawRegion(canvas, region, paint);
}
private void drawRegion(Canvas canvas, Region region, Paint paint) {
RegionIterator iterator = new RegionIterator(region);
Rect rect = new Rect();
while (iterator.next(rect)) {
canvas.drawRect(rect, paint);
}
}
}

左侧分别画出了所构造的椭圆和矩形,二者相交之后,所画出的 Region 对象是如右侧图 像所示的椭圆上部分。

1.4.2 枚举区域——RegionIterator 类

对于特定的区域,可以使用多个矩形来表示其大致形状。事实上,如果矩形足够小,一定数量的矩形就能够精确表示区域的形状。也就是说,一定数量的矩形所合成的形状也可以代表区域的形状。RegionIterator 类就实现了获取组成区域的矩形集的功能。

1
2
3
4
// 构造函数:根据区域构建对应的矩形集。
RegionIterator(Region region)
// 获取下一个矩形,将结果保存在参数 Rect r 中。
boolean next(Rect r)

前面提到,由于在 Canvas 中没有直接绘制 Region 的函数,想要绘制一个区域,就只能通过 RegionIterator 类构造矩形集来逼近显示区域,所以 drawRegion() 函数的具体实现如下:

1
2
3
4
5
6
7
private void drawRegion (Canvas canvas, Region rgn, Paint paint) {
RegionIterator iter = new RegionIterator(rgn);
Rect r = new Rect();
while (iter.next(r)) {
canvas.drawRect(r, paint);
}
}

首先根据区域构造一个矩形集,然后利用 next(Rect r) 函数来逐个获取所有矩形并绘制出来,最终得到的就是整个区域。如果我们想画一个椭圆区域,并且把画笔样式从 FILL 改为 STROKE,则效果更清楚。

1
2
3
4
5
6
7
8
9
10
11
Paint paint = new Paint();
paint.setColor(Color.RED);
paint.setStyle(Paint.Style.STROKE);
// 构造一条椭圆路径
Path ovalPath = new Path();
RectF rect = new RectF(50, 50, 200, 500);
ovalPath.addOval(rect, Path.Direction.CCW);
// 构造椭圆区域
Region rgn = new Region();
rgn.setPath(ovalPath, new Region(50, 50, 200, 500));
drawRegion(canvas,rgn,paint);

在代码中,同样先构造了一条椭圆路径,然后在形成 Region 时传入一个与构造的椭圆区域相同大小的矩形,所以取交集之后的结果就是椭圆路径所对应的区域。效果如下图所示:

从效果图中可以明显看出,在绘制 Region 对象时,其实就是先将其转换成矩形集,然后利用画笔将每个矩形画出来而已。

1.4.3 区域相交

Region 不是用来绘图的,而是在区域的相交操作中。

1. union() 函数

1
boolean union(Rect r)

该函数用于与指定矩形取并集,即将 Rect 所指定的矩形加入当前区域中。

示例:

1
2
3
4
5
6
7
paint = new Paint();
paint.setColor(Color.RED);
paint.setStyle(Paint.Style.FILL);
region = new Region(20, 20, 200, 100);
region.union(new Rect(20, 20, 50, 300));
drawRegion(canvas, region, paint);

将横向、竖向两个矩形区域合并,效果图如下:

2. 区域操作

1
2
3
boolean op(Rect rect, Op op)
boolean op(int left, int top, int right, int bottom, Op op)
boolean op(Region, Op)

操作结果赋给当前的 Region 对象。如果计算成功,返回 true;否则返回 false。

Op 参数值及含义:

1
2
3
4
5
6
7
8
public enum Op {
DIFFERENCE(0), // 最终区域为 region1 与 region2 不同的区域
INTERSECT(1), // 最终区域为 region1 与 region2 相交的区域
UNION(2), // 最终区域为 region1 与 region2 组合在一起的区域
XOR(3), // 最终区域为 region1 与 region2 相交之外的区域
REVERSE_DIFFERENCE(4), // 最终区域为 region2 与 region1 不同的区域
REPLACE(5); // 最终区域为 region2 的区域
}

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
public class BaseView extends View {
private Paint paint, paintFill;
private Region region1,region2,region3,region4,region5,region6,region7,region8,region9,region10,region11,region12;
private Rect rect1,rect2,rect3,rect4,rect5,rect6,rect7,rect8,rect9,rect10,rect11,rect12;
public BaseView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
paint = generatePaint(Color.RED, Paint.Style.STROKE, 2);
paintFill = generatePaint(Color.GREEN, Paint.Style.FILL, 0);
rect1 = new Rect(100, 140, 400, 240);
rect2 = new Rect(200, 40, 300, 340);
rect3 = new Rect(500, 140, 800, 240);
rect4 = new Rect(600, 40, 700, 340);
rect5 = new Rect(900, 140, 1200, 240);
rect6 = new Rect(1000, 40, 1100, 340);
rect7 = new Rect(100, 540, 400, 640);
rect8 = new Rect(200, 440, 300, 740);
rect9 = new Rect(500, 540, 800, 640);
rect10 = new Rect(600, 440, 700, 740);
rect11 = new Rect(900, 540, 1200, 640);
rect12 = new Rect(1000, 440, 1100, 740);
region1 = new Region(rect1);
region2 = new Region(rect2);
region1.op(region2, Region.Op.DIFFERENCE);
region3 = new Region(rect3);
region4 = new Region(rect4);
region3.op(region4, Region.Op.INTERSECT);
region5 = new Region(rect5);
region6 = new Region(rect6);
region5.op(region6, Region.Op.UNION);
region7 = new Region(rect7);
region8 = new Region(rect8);
region7.op(region8, Region.Op.XOR);
region9 = new Region(rect9);
region10 = new Region(rect10);
region9.op(region10, Region.Op.REVERSE_DIFFERENCE);
region11 = new Region(rect11);
region12 = new Region(rect12);
region11.op(region12, Region.Op.REPLACE);
}
@Override
protected void onDraw(Canvas canvas) {
canvas.drawRect(rect1, paint);
canvas.drawRect(rect2, paint);
drawRegion(canvas, region1, paintFill);
canvas.drawRect(rect3, paint);
canvas.drawRect(rect4, paint);
drawRegion(canvas, region3, paintFill);
canvas.drawRect(rect5, paint);
canvas.drawRect(rect6, paint);
drawRegion(canvas, region5, paintFill);
canvas.drawRect(rect7, paint);
canvas.drawRect(rect8, paint);
drawRegion(canvas, region7, paintFill);
canvas.drawRect(rect9, paint);
canvas.drawRect(rect10, paint);
drawRegion(canvas, region9, paintFill);
canvas.drawRect(rect11, paint);
canvas.drawRect(rect12, paint);
drawRegion(canvas, region11, paintFill);
}
private void drawRegion(Canvas canvas, Region region, Paint paint) {
RegionIterator iterator = new RegionIterator(region);
Rect rect = new Rect();
while (iterator.next(rect)) {
canvas.drawRect(rect, paint);
}
}
private Paint generatePaint(int color, Paint.Style style, int width) {
Paint paint = new Paint();
paint.setColor(color);
paint.setStyle(style);
paint.setStrokeWidth(width);
return paint;
}
}

依次为:DIFFERENCE、INTERSECT、UNION、XOR、REVERSE_DIFFERENCE、REPLACE

3. op 的重载

1
2
boolean op(Rect rect, Region region, Op op)
boolean op(Region region1, Region region2, Region.Op op)

这两个函数允许我们传入两个 Region 对象进行区域操作,并将操作结果赋给当前的 Region 对象。同样,当操作成功时,返回 true;否则返回 false。

1
2
3
4
Region region1 = new Region(100,100,400,200);
Region region2 = new Region(200,0,300,300);
Region region = new Region();
region.op(region1, region2, Region.Op.INTERSECT);

在这里,将 region1、region2 相交的结果赋给 Region 对象。

1.4.4 其他函数

1. 几个判断方法

1
2
3
4
5
6
// 判断该区域是否为空
public boolean isEmpty();
// 判断该区域是否是一个矩阵
public boolean isRect();
// 判断该区域是否是多个矩阵的组合
public boolean isComplex();

2. getBound 系列函数

1
2
3
4
5
6
// 返回能够包裹当前路径的最小矩形
public Rect getBounds()
public boolean getBounds(Rect r)
// 返回当前矩形所对应的 Path 对象
public Path getBoundaryPath()
public boolean getBoundaryPath(Path path)

3. 是否包含

1
2
3
4
5
// 判断该区域是否包含某个点
public boolean contains(int x, int y);
// 判断该区域是否包含某个矩形
public boolean quickContains(Rect r)
public boolean quickContains(int left, int top, int right,int bottom)

4. 是否相交

1
2
3
4
5
// 判断该区域是否没有和指定矩形相交
public boolean quickReject(Rect r)
public boolean quickReject(int left, int top, int right, int bottom);
// 判断该区域是否没有和指定区域相交
public boolean quickReject(Region rgn)

5. 平移变换

1
2
3
4
5
6
7
// 将 Region 对象向 X 轴平移 dx 距离,向 Y 轴平移 dy 距离,
// 并将结果赋给当前的 Region 对象。X 轴向右是正方向,Y 轴向下是正方向。
public void translate(int dx, int dy)
// 将 Region 对象向 X 轴平移 dx 距离,向 Y 轴平移 dy 距离。
// 与上一个函数不同的是,该函数将结果赋给 dst 对象,
// 而当前 Region 对象的值保持不变。
public void translate(int dx, int dy, Region dst)